package hn.sigit.logic.general.impl;

import hn.sigit.dao.hnd.cadastre.HND_LayerDAO;
import hn.sigit.dao.hnd.cadastre.HND_ParcelDAO;
import hn.sigit.logic.general.GeneralHelper;
import hn.sigit.logic.general.ResourceBundleHelper;
import hn.sigit.model.commons.IParcel;
import hn.sigit.model.commons.ISpatialZone;
import hn.sigit.model.hnd.administrative.HND_SpatialRule;
import hn.sigit.model.hnd.cadastre.HND_LandUse;
import hn.sigit.model.hnd.cadastre.HND_Layer;
import hn.sigit.model.ladm.spatialunit.LA_Level;

import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
import javax.xml.transform.TransformerException;

import org.apache.commons.lang.StringEscapeUtils;
import org.geotools.data.DataUtilities;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.gml.producer.FeatureTransformer;
import org.geotools.referencing.CRS;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTWriter;

@Stateful
@Scope(value=ScopeType.SESSION)
@Name(GeneralHelper.NAME)
@AutoCreate
public class GeneralHelperImpl implements GeneralHelper, Serializable {
	private static final long serialVersionUID = 1L;
	private List<HND_Layer> layerList;
	
	private static final String JURISDICTION_CODE = "0503";
	
	@In
	private ResourceBundleHelper resBundle;

	@Override
	public String getRequestRootPath() {
		HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
		
		return "http://" + request.getServerName() + ":"
			+ request.getServerPort();
	}
	
	@Override
	public String getWorkingSRS() {
		//TODO: extraer de la configuracion del sistema en tablas
		return "EPSG:32616";
	}
	
	@Override
	public String getWorkingNamespace() {
		return "sigit";
	}
	
	@Override
	public String getRasterLayerName() {
		return String.format("%s:%s", getWorkingNamespace(), "orthophoto");
	}
	
	@Override
	public String getPrimaryWMSLayer() {
		return String.format("%s:%s", getWorkingNamespace(), "spatial_unit__reference_point");
	}
	
	@Override
	public String getJurisdictionCode() {
		return JURISDICTION_CODE;
	}
	
	@Override
	public String getRequestRootMapServer() {
		HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
		
		//It assumes both SIGIT and the map server and in the same host
		//Port 8080 for map server will be standard for SIGIT deployment
		return String.format("http://%s:8080", request.getServerName()); 
	}
	
	@Override
	public String getOpenLayersPath() {
		return getRequestRootPath() + "/openlayers";
	}
	
	@Override
	public String getOpenLayersImgPath() {
		return getOpenLayersPath() + "/img";
	}
	
	@Override
	public String getOpenLayersThemeDefaultImgPath() {
		return getOpenLayersPath() + "/theme/default/img";
	}

	@Override
	public String getOpenLayersJS() {
		return getOpenLayersPath() + "/OpenLayers.js";
	}
	
	@Override
	public String getWmsCapabilitiesUrl111() {
		return getWmsUrl() + "?service=wms&version=1.1.1&request=GetCapabilities";
	}
	
	@Override
	public String getWmsUrl() {
		return getRequestRootMapServer() + "/geoserver/wms";
	}
	
	@Override
	public String formatNullText(String text) {
		return text != null ? text : "-";
	}
	
	@Override
	public String formatEmptyText(Object value) {
		if (value == null || value.equals(""))
			return resBundle.loadMessage("global.empty");
		
		return value.toString();
	}
	
	@Override
	public boolean isParcelInstance(ISpatialZone spatialZone) {
		return spatialZone instanceof IParcel;
	}

	@Override
	public String formatZoneNameText(ISpatialZone spatialZone) {
		return getZoneUsefulName(spatialZone);
	}

	@Override
	public String formatLevelText(LA_Level level) {
		return level != null ? formatNullText(level.getName()) : "-";
	}

	@Override
	public String formatLandUseText(HND_LandUse landUse) {
		return landUse != null ? formatNullText(landUse.getFullName()) : "-";
	}

	@Override
	public String getZoneUsefulName(ISpatialZone spatialZone) {
		String retval = "";
		
		if (spatialZone != null) {
			if (spatialZone instanceof IParcel)
				retval = "" + ((IParcel) spatialZone).getFieldTab() + " / " + (((IParcel) spatialZone).getCadastralKey() != null ? ((IParcel) spatialZone).getCadastralKey() : "(sin clave catastral)");
			else
				retval = spatialZone.getZoneName();
		}
		return retval;
	}
	
	@Override
	public String spatialRuleText(HND_SpatialRule sr) {
		if (sr == null) return "-";
		
		String op = "";
		switch (sr.getAction()) {
		case ASTERISK:
			op = "*";
			break;
		case PLUS:
			op = "+";
			break;
		case MINUS:
			op = "-";
			break;
		}

		return String.format("%s (%s)", sr.getCode(), op);
	}
	
	@Override
	public String spatialRuleDetailsText(HND_SpatialRule sr) {
		if (sr == null) return "-";
		
		ResourceBundleHelperImpl resBundle = new ResourceBundleHelperImpl();
		
		return String.format("%s - %s. {%s, %s} %s {%s, %s}: %s",
				sr.getCode(),
				sr.getDescription() != null ? sr.getDescription() : "", 
				sr.getLandUseOperand1() != null ? resBundle.loadMessage(sr.getLandUseOperand1().getName()) : "-",
				sr.getLevelOperand1() != null ? resBundle.loadMessage(sr.getLevelOperand1().getName()) : "-",
				resBundle.loadMessage(sr.getRuleOperator().name()),
				sr.getLandUseOperand2() != null ? resBundle.loadMessage(sr.getLandUseOperand2().getName()) : "-",
				sr.getLevelOperand2() != null ? resBundle.loadMessage(sr.getLevelOperand2().getName()) : "-",
				resBundle.loadMessage(sr.getAction().name()));
	}

	//used to execute EL expression methods denoted by "elMethod" and do action
	@Override
	public String executeELMethod(String elMethod, String action) {
		MethodExpression me;
		
		Application app = FacesContext.getCurrentInstance().getApplication();
		ExpressionFactory exprFactory = app.getExpressionFactory();
		ELContext elCtx = FacesContext.getCurrentInstance().getELContext();

		//third parameter is set to null meaning that we do not care for the
		//result type... and no expected parameters for fourth parameter,
		//however we can not use null for this argument
		me = exprFactory.createMethodExpression(elCtx, "#{" + elMethod + "}", null, new Class[] {});
		me.invoke(elCtx, null);
		
		return action;
	}
	
	//used to retrieve parcel's WKT to render features in OpenLayers
	@Override
	//@WebRemote
	public String getParcelWKT(long parcelId) {
		IParcel parcel = HND_ParcelDAO.loadParcelById(parcelId);
		WKTWriter wktWriter = new WKTWriter();
		
		return wktWriter.write(parcel.getShape());
	}
	
	@Override
	public List<HND_Layer> getLayerList() {
		if (layerList == null) {
			layerList = HND_LayerDAO.loadLayers(true);
		}
		return layerList;
	}

	@Override
	public String drawLayerJS(String mapControlName, HND_Layer layer) {
		StringBuffer sb = new StringBuffer();
		sb.append(String.format(
				"var wms = new OpenLayers.Layer.WMS(" +
				"	'%s'," +
				"	'%s'," +
				"	{layers: '%s', transparent: true, format: 'image/png'}," +
				"	{projection: '%s'});" +
				"wms.id = '%s';" +
				"%s.addLayer(wms);",
				
				layer.getName(),
				getWmsUrl(),
				layer.getFullQualifiedName(),
				getWorkingSRS(),
				layer.getFullQualifiedName(),
				mapControlName
		));
		return sb.toString();
	}
	
	@Override
	public String drawAllLayersJS(String mapControlName) {
		StringBuffer sb = new StringBuffer();
		for (HND_Layer layer : getLayerList()) {
			sb.append( drawLayerJS(mapControlName, layer) );
		}
		return sb.toString();
	}
	
	@Override
	public double[] calcZoomEnvelope(List<Geometry> envelopeList) {
		double x1 = Double.MAX_VALUE;
		double y1 = Double.MAX_VALUE;
		double x2 = Double.MIN_VALUE;
		double y2 = Double.MIN_VALUE;
		
		for (Geometry envelope : envelopeList) {
			for (Coordinate coord : envelope.getCoordinates()) {
				if (coord.x < x1) x1 = coord.x;
				if (coord.y < y1) y1 = coord.y;
				if (coord.x > x2) x2 = coord.x;
				if (coord.y > y2) y2 = coord.y;
			}
		}
		
		double dx = Math.abs(x2 - x1);
		double dy = Math.abs(y2 - y1);
		
		x1 -= dx * 0.15;
		y1 -= dy * 0.15;
		x2 += dx * 0.15;
		y2 += dy * 0.15;
		
		
		return new double[] {x1, y1, x2, y2};
	}
	
	@Override
	public <SZ extends ISpatialZone> double[] calcSZsZoomEnvelope(List<SZ> szList) {
		return calcZoomEnvelope(calcEnvelopeFromSZList(szList));
	}
	
	@Override
	public <SZ extends ISpatialZone> List<Geometry> calcEnvelopeFromSZList(List<SZ> szList) {
		List<Geometry> geomList = new ArrayList<Geometry>();
		for (SZ sz : szList)
			geomList.add(sz.getShape());
		
		return geomList;
	}

	
	@Override
	public <SZ extends ISpatialZone> String getSpatialZonesGML(List<SZ> spatialZoneList, int neighborLevel) {
		if (spatialZoneList == null) return "";
		
		String retval = "";

		FeatureTransformer transform = new FeatureTransformer();
		transform.setEncoding(Charset.defaultCharset());
		transform.setIndentation(4);
		transform.setGmlPrefixing(true);
		
		try {
			SimpleFeatureType TYPE;
			TYPE = DataUtilities.createType("Polygon", "geom:Polygon,label:String,detailed_label:String,name:String,neighborLevel:Integer");
			String prefix = "sigit";//(String) TYPE.getUserData().get("prefix");
			String namespace = "http://localhost";//TYPE.getName().getNamespaceURI();
			transform.getFeatureTypeNamespaces().declareDefaultNamespace(prefix, namespace);
			transform.addSchemaLocation(prefix, namespace);
			
			String srsName = CRS.toSRS(TYPE.getCoordinateReferenceSystem());
			if (srsName != null)
				transform.setSrsName(srsName);
			
			SimpleFeatureCollection collection = FeatureCollections.newCollection("internal");
			for (ISpatialZone spatialZone : spatialZoneList) {
				SimpleFeature sf;
				sf = SimpleFeatureBuilder.build(TYPE,
						new Object[] { (Polygon) spatialZone.getShape(), "name1" },
						String.valueOf(spatialZone.getSuID()));
				sf.setAttribute("label", getSZUsefulLabel(spatialZone, false));
				sf.setAttribute("detailed_label", getSZUsefulLabel(spatialZone, true));
				sf.setAttribute("name", getSZUsefulName(spatialZone));
				sf.setAttribute("neighborLevel", neighborLevel);
				collection.add(sf);
				ByteArrayOutputStream baos = new ByteArrayOutputStream(16834);
				
				transform.setCollectionBounding(true);
				transform.transform(collection, baos);
				
				retval = baos.toString();
			}
		}
		catch (SchemaException se) {
			se.printStackTrace();
			return null;
		}
		catch (TransformerException te) {
			te.printStackTrace();
			return null;
		}

		return retval;
	}
	
	private String getSZUsefulLabel(ISpatialZone sz, boolean detailed) {
		if (sz == null) return "";
		
		String label = "x";
		
		if (sz instanceof IParcel) {
			IParcel p = (IParcel) sz;

			if (detailed)
				label = String.format("%s - %s",
						p.getMunicipalKey() != null ? p.getMunicipalKey() : p.getFieldTab(),
						p.getCadastralKey() != null ? p.getCadastralKey() : getResBundle().loadMessage("dataentry.no_cadastralkey"));
			else
				label = p.getCadastralKey() != null ? p.getCadastralKey()
						: (p.getMunicipalKey() != null ? p.getMunicipalKey()
								: (p.getFieldTab() != null ? p.getFieldTab().toString() : "x"));
		}
		else {
			label = getSZUsefulName(sz);
		}
		
		return label;
	}
	
	private String getSZUsefulName(ISpatialZone sz) {
		if (sz == null) return "";
		
		return sz.getZoneName() != null && !sz.getZoneName().trim().equals("") ? sz.getZoneName() : "(sin nombre)";
	}
	
	@Override
	public ResourceBundleHelper getResBundle() {
		if (resBundle == null) {
			resBundle = new ResourceBundleHelperImpl();
		}
		return resBundle;
	}
	
	@Override
	public String getDateTimePattern() {
		return "dd/MM/yyyy h:mm a";
	}
	
	@Override
	public String getTimeZone() {
		return "GMT-06:00";
	}
	
	@Override
	public String escapeJS(String txt) {
		return StringEscapeUtils.escapeJavaScript(txt);
	}
	
	@Override
	public String escapeJS2(String txt) {
		return StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeJavaScript(txt));
	}
	

	@Destroy @Remove
	public void destroy() {}
}
